#include <stdio.h>
#include <errno.h>

#include "INIParser.h"

INIParser::INIParser() {
		_logger = log4cxx::Logger::getLogger("shadow.sutil.iniparser");
}

INIParser::~INIParser() {}

int INIParser::read_whole_file_to_buf(const char * file_path, string & buf) {
		if (NULL == file_path || strlen(file_path) < 1) {
				LOG4CXX_ERROR(_logger, "parse ini file error, invalid ini file path");
				return -1;
		}
		FILE * fp = fopen(file_path, "r");
		if (NULL == fp) {
				LOG4CXX_ERROR(_logger, "can't open file:" << file_path << ", errno:" << errno);
				return -2;
		}
		int res = fseek(fp, 0, SEEK_END);
		if (0 != res) {
				fclose(fp);
				LOG4CXX_ERROR(_logger, "seek file to end error, file:" << file_path << ", errno:" << errno);
				return -3;
		}
		int size = ftell(fp);
		if (-1 == size) {
				fclose(fp);
				LOG4CXX_ERROR(_logger, "ftell file error, file:" << file_path << ", errno:" << errno);
				return -4;
		}
		res = fseek(fp, 0, SEEK_SET);
		if (0 != res) {
				fclose(fp);
				LOG4CXX_ERROR(_logger, "seek file to begin error, file:" << file_path << ", errno:" << errno);
				return -5;
		}
		char * t = new char[size];
		int read = fread(t, size, 1, fp);
		fclose(fp);
		buf = t;
		if (1 != read) {
				LOG4CXX_ERROR(_logger, "read ini file fail, file:" << file_path << ", total size:" << size << \
								", read size:" << read);
				return -6;
		}
		return 1;

}

int INIParser::parse(const char * file_path) {
		if (NULL == file_path || strlen(file_path) < 1) {
				LOG4CXX_ERROR(_logger, "parse ini file error, invalid ini file path");
				return -1;
		}
		string buf;
		int res = read_whole_file_to_buf(file_path, buf);
		if (res < 0) return -2;
		_ini_file_path = file_path;
		list<string> lines;
		res = split_buf_to_lines(buf.c_str(), buf.size(), lines);
		if (res < 0) return -3;
		string curr_session = SUTIL_INI_DEFAULT_SESSION_NAME;
		_contents.insert(map<string, StringMap>::value_type(SUTIL_INI_DEFAULT_SESSION_NAME, StringMap()));
		char first_char;
		string tt, key, value;
		for (list<string>::const_iterator iter = lines.begin(); lines.end() != iter; iter++) {
				if (iter->size() < 1) continue;
				first_char = (*iter)[0];
				if (SUTIL_INI_COMMENT_CHAR == first_char) continue;
				if (is_session_line(iter->c_str(), iter->size(), tt)) {
						curr_session = tt;
						map<string, StringMap>::const_iterator iter1 = _contents.find(curr_session);
						if (_contents.end() == iter1) {
								_contents.insert(map<string, StringMap>::value_type(curr_session, StringMap()));
						}
				}
				else {
						res = split_line_key_and_value(iter->c_str(), iter->size(), key, value);
						if (res < 0 || key.size() < 1) continue;
						map<string, StringMap>::iterator iter2 = _contents.find(curr_session);
						if (_contents.end() == iter2) {
								LOG4CXX_ERROR(_logger, "parse ini file error, session:" << curr_session << \
												" not found in previous content, maybe a programing bug");
								return -4;
						}
						iter2->second.insert(StringMap::value_type(key, value));
				}
		}
		return 1;
}

int INIParser::split_buf_to_lines(const char *cbuf, const int len, list<string> & lines) {
		if (NULL == cbuf || len < 0) {
				LOG4CXX_ERROR(_logger, "split_buf_to_lines error, invaid args");
				return -1;
		}
		if (len < 1) return 1;
		string buf(cbuf, len);
		int p0(0), p1(0);
		int line_len(0);
		for (int i = 0; i < len; i++) {
				char c = buf[i];
				if ('\n' == c) {
						p1 = i;
						if ((0 != i) && ('\r' == buf[i - 1])) line_len = p1 - p0 - 1;
						else line_len = p1 - p0;
						lines.push_back(buf.substr(p0, line_len));
						p0 = p1 + 1;
				}
		}
		if (len > p0) {
				if ('\r' == buf[len - 1]) line_len = len - p0 - 1;
				else line_len = len - p0;
				lines.push_back(buf.substr(p0, line_len));
		}
		return 1;
}

bool INIParser::is_session_line(const char * cbuf, const int len, string & session_name) {
		if (NULL == cbuf || len < 0) {
				LOG4CXX_ERROR(_logger, "is_session_line error, invalid args");
				return false;
		}
		string buf(cbuf, len);
		string::size_type p0 = buf.find(SUTIL_INI_SESSION_START_CHAR);
		if (string::npos == p0) return false;
		string::size_type p1 = buf.find(SUTIL_INI_SESSION_END_CHAR, p0 + 1);
		if (string::npos == p1) return false;
		int session_len = p1 - p0 - 1;
		if (session_len < 1) return false;
		string tt1 = buf.substr(p0 + 1, session_len);
		string tt2;
		trim(tt1.c_str(), tt1.size(), tt2);
		if (tt2.size() < 1) return false;
		session_name = tt2;
		return true;
}

int INIParser::trim(const char * cbuf, const int len, string & out) {
		if (NULL == cbuf || len < 1) {
				LOG4CXX_ERROR(_logger, "trim error, invalid args");
				return -1;
		}
		int first_not_white_space_pos(-1), last_not_white_space_pos(-1);
		for (int i = 0; i < len; i++) {
				char c = cbuf[i];
				if (isspace(c) == 0) {
						first_not_white_space_pos = i;
						break;
				}
		}
		for (int i = len - 1; i >= 0; i--) {
				char c = cbuf[i];
				if (isspace(c) == 0) {
						last_not_white_space_pos = i;
						break;
				}
		}
		if (first_not_white_space_pos < 0 || last_not_white_space_pos < 1 || \
						last_not_white_space_pos <= first_not_white_space_pos) {
				out = "";
				return 1;
		}
		out.assign(cbuf + first_not_white_space_pos, last_not_white_space_pos - first_not_white_space_pos + 1);
		return 1;
}

int INIParser::split_line_key_and_value(const char * cbuf, const int len, string & key, string & value) {
		if (NULL == cbuf || len < 0) {
				LOG4CXX_ERROR(_logger, "split_line_key_and_value error, invalid args");
				return -1;
		}
		string buf(cbuf, len);
		string::size_type p = buf.find(SUTIL_INI_KEY_VALUE_SEP_CHAR);
		if (string::npos == p) {
				trim(buf.c_str(), buf.size(), key);
				value = "";
				return 1;
		}
		string t1 = buf.substr(0, p);
		string t2 = buf.substr(p + 1);
		trim(t1.c_str(), t1.size(), key);
		trim(t2.c_str(), t2.size(), value);
		return 2;
}

map<string, StringMap> & INIParser::get_contents() {
		return _contents;
}

int INIParser::get_int_value(const char * session, const char * key, int & value) {
		if (NULL == key || strlen(key) < 1) {
				LOG4CXX_ERROR(_logger, "get_int_value error, invalid key");
				return -1;
		}
		string raw_string;
		int res = get_raw_value(session, key, raw_string);
		if (res < 0) return -2;
		value = atoi(raw_string.c_str());
		return 1;
}

int INIParser::get_string_value(const char * session, const char * key, string & value) {
		if (NULL == key || strlen(key) < 1) {
				LOG4CXX_ERROR(_logger, "get_string_value error, invalid key");
				return -1;
		}
		string raw_string;
		int res = get_raw_value(session, key, raw_string);
		if (res < 0) return -2;
		value = raw_string.c_str();
		return 1;
}

int INIParser::get_bool_value(const char * session, const char * key, bool & value) {
		if (NULL == key || strlen(key) < 1) {
				LOG4CXX_ERROR(_logger, "get_bool_value error, invalid key");
				return -1;
		}
		string raw_string, t;
		int res = get_raw_value(session, key, raw_string);
		if (res < 0) return -2;
		to_lower(raw_string.c_str(), raw_string.size(), t);
		if (t == "false") {
				value = false;
				return 1;
		}
		else if (t == "true") {
				value = true;
				return 1;
		}
		LOG4CXX_ERROR(_logger, "get_bool_value key:" << key << ", value:" << value << ", value is not true or false");
		return -3;
}

int INIParser::get_long_long_value(const char * session, const char * key, long long & value) {
		if (NULL == key || strlen(key) < 1) {
				LOG4CXX_ERROR(_logger, "get_long_long_value error, invalid key");
				return -1;
		}
		string raw_string;
		int res = get_raw_value(session, key, raw_string);
		if (res < 0) return -2;
		value = atoll(raw_string.c_str());
		return 1;
}

int INIParser::to_lower(const char * str, const int len, string & out) {
		if (NULL == str || len < 0) {
				LOG4CXX_ERROR(_logger, "to_lower error, invalid args");
				return -1;
		}
		out.clear();
		for (int i = 0; i < len; i++) {
				out.append(1, tolower(str[i]));
		}
		return 1;
}

int INIParser::get_raw_value(const char * csession, const char * key, string & value) {
		if (NULL == key || strlen(key) < 1) {
				LOG4CXX_ERROR(_logger, "get_raw_value error, invalid args:key");
				return -1;
		}
		string session;	
		if (NULL == csession) session = SUTIL_INI_DEFAULT_SESSION_NAME;
		else session = csession;
		map<string, StringMap>::const_iterator iter = _contents.find(session);
		if (_contents.end() == iter) return -2;
		StringMap::const_iterator iter1 = iter->second.find(key);
		if (iter->second.end() == iter1) return -3;
		value = iter1->second;
		return 1;
}
